refactor: unified polymorphic domain + registry#1983
Conversation
Split `RegistryId` into a union of `ENSv1RegistryId`, `ENSv1VirtualRegistryId`,
and `ENSv2RegistryId`. Add corresponding `makeENSv1RegistryId`,
`makeENSv2RegistryId`, and `makeENSv1VirtualRegistryId` constructors, and keep
`makeRegistryId` as a union-returning helper for callsites that genuinely can't
narrow (e.g. client-side cache key reconstruction).
Reshape `ENSv1DomainId` from `Node` to `${ENSv1RegistryId}/${node}` so ENSv1
domains are addressable in the same namegraph model as ENSv1VirtualRegistry.
`makeENSv1DomainId` now takes `(AccountId, Node)` — breaking change for all
callers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the split `v1_domains` + `v2_domains` tables with a single polymorphic `domains` table keyed by `DomainId` and discriminated by `domainType` enum (`"ENSv1Domain"` | `"ENSv2Domain"`). Drop `domain.parentId`; ENSv1 parent traversal now flows through `registryCanonicalDomain` uniformly with ENSv2. `tokenId` becomes nullable (non-null iff ENSv2). Make `registries` polymorphic: add `registryType` enum (`"ENSv1Registry"` | `"ENSv1VirtualRegistry"` | `"ENSv2Registry"`), add nullable `node` column (non-null iff virtual), replace the unique `(chainId, address)` constraint with a plain index so virtual Registries keyed by node can share (chainId, address) with their concrete parent. Widen `registryCanonicalDomain.domainId` from `ENSv2DomainId` to the unified `DomainId`. Add `getENSv1RootRegistryId` / `maybeGetENSv1RootRegistryId` / `maybeGetENSv1Registry` helpers mirroring the v2 equivalents; narrow v2 helpers to use `makeENSv2RegistryId`. Update the ensdb-sdk drizzle test to reference the unified `domain` export. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend `managed-names.ts`: `CONTRACTS_BY_MANAGED_NAME` now maps `Name` to
`{ registry, contracts }`, `getManagedName(contract)` returns
`{ name, node, registry }` so any Registrar / Controller / NameWrapper handler
can resolve the concrete ENSv1 Registry that governs its namegraph. Add the
ENS Root (`""`) Managed Name group covering the mainnet ENSv1Registry and
ENSv1RegistryOld; include each shadow Registry (Basenames, Lineanames) in its
respective Managed Name group. Groups for namespaces that don't ship a given
shadow Registry are omitted entirely.
ENSv1 `handleNewOwner`: upsert the concrete ENSv1 Registry row, pick
`parentRegistryId` as the concrete Registry when `parentNode` is the Managed
Name and as an `ENSv1VirtualRegistry` keyed by `parentNode` otherwise. When
the parent is virtual, also upsert the virtual Registry row and the
`registryCanonicalDomain` self-link so reverse traversal works uniformly with
ENSv2. Combine domain upsert with `rootRegistryOwner` update into one query
via `onConflictDoUpdate`. Canonicalize ENSv1Registry / ENSv1RegistryOld events
through `getManagedName(...).registry` — ENSRegistryWithFallback proxies
reads, so both contracts face the same logical namegraph and should write into
the same Registry ID.
All remaining v1 handlers (Transfer / NewTTL / NewResolver, BaseRegistrar,
NameWrapper, RegistrarController, protocol-acceleration ENSv1Registry /
ThreeDNSToken) update to the two-arg `makeENSv1DomainId(registry, node)`.
ENSv2 `handleRegistrationOrReservation`: switch `makeRegistryId` to
`makeENSv2RegistryId`, add `type: "ENSv2Registry"` to the Registry insert and
`type: "ENSv2Domain"` to the Domain insert.
Update `domain-db-helpers.materializeENSv1DomainEffectiveOwner` to write
through the unified `domain` table. Update the managed-names test to assert
the new `registry` return field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Context & loaders
- Drop `v1CanonicalPath` + `v2CanonicalPath` loaders in favour of a single
`canonicalPath` loader backed by `getCanonicalPath(domainId)`.
Canonical path
- Replace `getV1CanonicalPath` + `getV2CanonicalPath` with a single recursive
CTE over `domain` + `registryCanonicalDomain`. Recursion terminates naturally:
roots have no `registryCanonicalDomain` entry, so the JOIN fails when we
reach one. Canonicality is decided by the final `tld.registry_id === root`
check. MAX_DEPTH guards against corrupted state.
Interpreted-name lookup (`get-domain-by-interpreted-name.ts`)
- Collapse the ENSv1 / ENSv2 branches into one `traverseFromRoot(root, name)`
helper. Both lineages hop via `domain.subregistryId` (ENSv1 Domains now set
this to their managed VirtualRegistry, symmetric with ENSv2 domains' declared
subregistries). The starting root picks v1 vs v2 lineage; v1 and v2 registry
IDs are disjoint, so no cross-contamination.
Find-domains layers
- `base-domain-set.ts`: single select over `domain`; `parentId` derived via
`registryCanonicalDomain` uniformly for v1 and v2.
- `filter-by-registry.ts`: simplify comment (no v1/v2 distinction).
- `filter-by-canonical.ts`: all domains have a `registryId` now; canonicality
reduces to `INNER JOIN` against the canonical-registries CTE.
- `filter-by-name.ts`: collapse `v1DomainsByLabelHashPath` +
`v2DomainsByLabelHashPath` into one CTE over `registryCanonicalDomain`.
- `canonical-registries-cte.ts`: union v1 + v2 roots as base cases; recursive
step uses `d.subregistry_id` uniformly.
Schemas
- `schema/domain.ts`: `DomainInterfaceRef` becomes a loadable interface with a
single `ensDb.query.domain.findMany` loader. `DomainInterface = Omit<Domain,
"tokenId" | "node" | "rootRegistryOwnerId">`. Variant types tightened via
`RequiredAndNotNull` / `RequiredAndNull` to encode invariants
(`ENSv1Domain.{node: Node, tokenId: null}`;
`ENSv2Domain.{tokenId: bigint, node: null, rootRegistryOwnerId: null}`).
`parent` moves onto the interface via `ctx.loaders.canonicalPath`; expose
`ENSv1Domain.node` as a first-class GraphQL field.
- `schema/registry.ts`: new `RegistryInterfaceRef` with `ENSv1Registry`,
`ENSv1VirtualRegistry`, `ENSv2Registry` implementations; shared fields
(`id`, `type`, `contract`, `parents`, `domains`, `permissions`). `parents`
uses `eq(domain.subregistryId, parent.id)` for virtual v1 and v2 (both set
`subregistryId`), and `eq(domain.registryId, parent.id)` for concrete v1.
`ENSv1VirtualRegistryRef` exposes `node: Node`.
- `schema/query.ts`: `registry(by: {contract})` does a DB lookup filtered by
`type IN (ENSv1Registry, ENSv2Registry)` — virtual Registries share
`(chainId, address)` with their concrete parent and aren't addressable via
contract alone. Dev-only `v1Domains` / `v2Domains` filter by `d.type`.
- Swap `RegistryRef` → `RegistryInterfaceRef` in `query.ts` and
`registry-permissions-user.ts`.
- `schema/registration.ts`: `WrappedBaseRegistrarRegistration.tokenId` loads
the domain via the `DomainInterfaceRef` dataloader and reads `domain.node`.
Supporting changes
- `ensdb-sdk` schema: add `domain.node: Hex` (non-null iff ENSv1Domain).
- `ensindexer` ENSv1 `handleNewOwner`: write `node` on domain upsert and set
parent domain's `subregistryId` to the VirtualRegistry when upserting it
(so forward traversal + canonical-registries CTE work uniformly with v2).
- `ensnode-sdk`: add `RequiredAndNull<T, K>` helper type (symmetric to
`RequiredAndNotNull`) for encoding "null in this variant" invariants.
Regenerate pothos generated files (`schema.graphql`, `introspection.ts`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 3ddddcf The changes in this PR will be included in the next version bump. This PR includes changesets to release 24 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughConsolidates ENSv1/ENSv2 domain and registry models into unified tables and polymorphic GraphQL interfaces; unifies canonical-path traversal and loaders to use namespace-root seeding; makes indexer handlers registry-aware; updates ID constructors, SDK helpers, tests, and docs to the new unified model. Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(220,240,255,0.5)
participant Client
end
rect rgba(200,255,220,0.5)
participant GraphQL as "Omnigraph API"
end
rect rgba(255,235,205,0.5)
participant Loaders as "Context Loaders\n(canonicalPath)"
end
rect rgba(255,215,230,0.5)
participant DB as "ensIndexer DB\n(domains, registries)"
end
Client->>GraphQL: request Domain / Query.root / registry
GraphQL->>Loaders: load canonicalPath(domainId)
Loaders->>DB: recursive SQL traversal seeded by getRootRegistryIds(namespace)
DB-->>Loaders: canonical path (list of DomainId) or null
Loaders-->>GraphQL: CanonicalPath | Error | null
GraphQL->>DB: query domain/registry rows via unified `domain`/`registries`
DB-->>GraphQL: domain/registry records
GraphQL-->>Client: resolved response (polymorphic types)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR refactors the ENS data model and API surface to unify ENSv1 + ENSv2 domains into a single polymorphic domain table, introduces a polymorphic Registry model (including ENSv1 virtual registries), and updates the indexer + ENS API to traverse/query the unified namegraph—targeting correctness for multi-registry conflicts (#205) and improving find-domains architecture/perf (#1877) while advancing DomainId terminology (#1511).
Changes:
- Unifies DB schema from
v1Domain/v2Domainintodomain(typed by enum), and makesregistrypolymorphic (ENSv1 concrete / ENSv1 virtual / ENSv2). - Changes ENSv1 identifiers to be registry-qualified (
ENSv1DomainId = ${ENSv1RegistryId}/${node}) and adds new RegistryId constructors. - Updates indexer handlers + ENS API (GraphQL schema, loaders, find-domains layers, canonical path + interpreted-name traversal) to operate on the unified model and expose new GraphQL interfaces/fields (
Registryinterface,Domain.parent).
Reviewed changes
Copilot reviewed 34 out of 36 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/enssdk/src/omnigraph/generated/schema.graphql | Updates generated GraphQL schema: Domain.parent, ENSv1Domain.node, Registry becomes an interface with ENSv1/virtual/v2 implementations. |
| packages/enssdk/src/omnigraph/generated/introspection.ts | Updates generated introspection JSON to match new interfaces/types. |
| packages/enssdk/src/lib/types/ensv2.ts | Introduces branded ENSv1RegistryId/ENSv2RegistryId/ENSv1VirtualRegistryId and changes ENSv1 domain id shape. |
| packages/enssdk/src/lib/ids.ts | Adds makeENSv1RegistryId / makeENSv2RegistryId / makeENSv1VirtualRegistryId; changes makeENSv1DomainId signature to include registry. |
| packages/ensnode-sdk/src/shared/types.ts | Adds RequiredAndNull TS helper used for discriminated polymorphic models. |
| packages/ensnode-sdk/src/shared/root-registry.ts | Adds ENSv1 root registry id helpers and switches ENSv2 root helper to makeENSv2RegistryId. |
| packages/ensdb-sdk/src/lib/drizzle.test.ts | Updates schema cloning tests to reflect table rename (domain). |
| packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts | Core schema refactor: new domain + polymorphic registry, new enums, updated relations/indexes, registryCanonicalDomain.domainId to unified DomainId. |
| apps/ensindexer/src/plugins/protocol-acceleration/handlers/ThreeDNSToken.ts | Updates ENSv1 domain id creation to include registry. |
| apps/ensindexer/src/plugins/protocol-acceleration/handlers/ENSv1Registry.ts | Canonicalizes ENSv1 registry source and updates domain id creation to include registry. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts | Writes ENSv2 domains into unified domain table and inserts registries with polymorphic type. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts | Updates ENSv1 domain id creation to include registry from managed-name group. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts | Updates ENSv1 domain id creation to include registry for wrapper events. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts | Implements ENSv1 virtual registries + unified domain writes; canonicalizes ENSv1RegistryOld vs new registry. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts | Retargets reads/writes to unified domain table and updates ENSv1 id creation. |
| apps/ensindexer/src/lib/managed-names.ts | Expands managed-name mapping to include concrete registry per group; adds ENS root group and shadow registries. |
| apps/ensindexer/src/lib/managed-names.test.ts | Updates tests for new registry return from getManagedName. |
| apps/ensindexer/src/lib/ensv2/domain-db-helpers.ts | Updates effective-owner materialization to write unified domain table. |
| apps/ensapi/src/omnigraph-api/schema/registry.ts | Replaces Registry object with Registry interface + ENSv1/virtual/v2 object types; updates parent/domain connections. |
| apps/ensapi/src/omnigraph-api/schema/registry-permissions-user.ts | Switches to RegistryInterfaceRef. |
| apps/ensapi/src/omnigraph-api/schema/registration.ts | Updates wrapped token id resolution to load ENSv1 domain and use domain.node. |
| apps/ensapi/src/omnigraph-api/schema/query.ts | Retargets v1/v2 domain list queries to unified table; updates Query.registry to resolve concrete registries via DB lookup. |
| apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts | Updates fixtures for ENSv1 id format (registry + node). |
| apps/ensapi/src/omnigraph-api/schema/domain.ts | Reworks Domain loader to unified domain table, adds Domain.parent, and updates ENSv1/ENSv2 type implementations. |
| apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts | Replaces separate v1/v2 lookup logic with unified forward traversal rooted at v1/v2 root registries. |
| apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts | Unifies canonical-path resolution into a single reverse-traversal CTE over domain + registryCanonicalDomain. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-registry.ts | Updates docs/semantics for unified registry filtering. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-parent.ts | Updates docs to match unified model (behavior unchanged). |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts | Replaces v1/v2 union traversal with a single traversal over unified domain table. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts | Switches canonical filtering to require membership in canonical registries set. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts | Replaces v1/v2 union base set with a single unified domain-based base set. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts | Replaces ENSv2-only canonical registry traversal with unified traversal rooted at v1 root (+ v2 root if present). |
| apps/ensapi/src/omnigraph-api/context.ts | Consolidates canonical-path loaders into a single canonicalPath loader. |
| SPEC-domain-model.md | Adds/updates design spec for unified polymorphic domain + registry model and migration notes. |
| .changeset/unified-domain-model.md | Declares major bumps and documents breaking changes (schema + id formats + GraphQL). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@greptile re-review |
Greptile SummaryThis PR unifies ENSv1 and ENSv2 domains/registries into a single polymorphic
Confidence Score: 3/5Not safe to merge — multiple P1 findings across the namegraph traversal, canonical path, and name filtering layers. One new P1 (FQDN search regression in filterByName) plus several P1s carried over from the previous review round (shadow-registry Domain.name returns only leaf label, getCanonicalPath shadow-registry termination, forward traversal of shadow ENSv1 registries). The refactor is architecturally sound but the traversal correctness issues are widespread enough to warrant careful validation before merge. apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts (FQDN search regression), apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts (shadow registry canonical path), packages/ensnode-sdk/src/shared/root-registry.ts (getRootRegistryIds shadow registry IDs) Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["ENSv1 NewOwner\n(parentNode = ENS_ROOT_NODE)"] -->|isTLD = true| B["Insert ENSv1Registry\n(concrete, no RCD entry)"]
A -->|isTLD = false| C["Insert ENSv1VirtualRegistry\n+ registryCanonicalDomain entry"]
C --> D["Update parent domain\nsubregistryId = virtualRegistryId"]
E["filterByName\ndomainsByLabelHashPath"] -->|pathLength = 1| F["Traverse via RCD\ndepth=1 → TLD domain ✓"]
E -->|pathLength ≥ 2 with TLD in concrete| G["depth=N step: TLD domain\nregistryId = concrete ENSv1Registry\nNO registryCanonicalDomain → JOIN fails\n❌ Returns empty"]
H["getCanonicalPath\nfoo.base.eth"] --> I["registryId = VirtualRegistry for base.eth\n= rootRegistryIds entry → stop immediately"]
I --> J["Path = [foo.base.eth.id] only\nDomain.name = 'foo' not 'foo.base.eth' ❌"]
K["getRootRegistryIds"] --> L["ENSv1 Root concrete registry ✓"]
K --> M["Basenames/Lineanames:\nmakeENSv1VirtualRegistry(shadowRegistry, managedNode)\n← terminates traversal at shadow root,\n not at concrete registry"]
Reviews (16): Last reviewed commit: "fix: support ensv1resolver fallback" | Re-trigger Greptile |
There was a problem hiding this comment.
Pull request overview
This PR unifies ENSv1/ENSv2 “Domain” and “Registry” modeling into a single polymorphic namegraph across the indexer DB schema and Omnigraph GraphQL API, enabling consistent canonical traversal and handling of shadow registries/subregistries while advancing the DomainId terminology/ID-shape refactor.
Changes:
- Unifies indexed storage into polymorphic
domains+registriestables (incl. ENSv1 virtual registries) and updates canonical traversal (forward + reverse) to operate on the unified graph. - Updates
enssdkID/type shapes (notably CAIP-shapedENSv1DomainIdand polymorphicRegistryId) and adds managed-name/root-registry helpers in@ensnode/ensnode-sdk. - Refactors ensindexer handlers + ensapi schema/resolvers + enskit cache resolvers/tests to use the unified types and new canonical-path logic.
Reviewed changes
Copilot reviewed 47 out of 49 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/enssdk/src/omnigraph/generated/schema.graphql | Updates Omnigraph schema: Domain gains parent, Registry becomes interface w/ v1/v1-virtual/v2 types, adds allDomains, makes root non-null. |
| packages/enssdk/src/omnigraph/generated/introspection.ts | Regenerates GraphQL introspection to match new schema/interface polymorphism. |
| packages/enssdk/src/lib/types/ensv2.ts | Introduces polymorphic RegistryId types and CAIP-shaped ENSv1DomainId/virtual-registry IDs. |
| packages/enssdk/src/lib/ids.ts | Adds constructors for new RegistryId variants and updates ENSv1 domain ID construction. |
| packages/ensnode-sdk/src/shared/types.ts | Adds RequiredAndNull<T, K> utility type. |
| packages/ensnode-sdk/src/shared/root-registry.ts | Adds getRootRegistryId/getRootRegistryIds and switches to explicit v1/v2 registry ID constructors. |
| packages/ensnode-sdk/src/shared/managed-names.ts | New managed-name registry-aware helper (maps contracts → managed name/node/registry). |
| packages/ensnode-sdk/src/registrars/lineanames-subregistry.ts | Removes legacy Lineanames subregistry helper (superseded by managed-name logic). |
| packages/ensnode-sdk/src/registrars/index.ts | Removes exports for deleted registrar-specific subregistry helpers. |
| packages/ensnode-sdk/src/registrars/ethnames-subregistry.ts | Removes legacy Ethnames subregistry helper (superseded by managed-name logic). |
| packages/ensnode-sdk/src/registrars/basenames-subregistry.ts | Removes legacy Basenames subregistry helper (superseded by managed-name logic). |
| packages/ensnode-sdk/src/index.ts | Exposes new shared managed-name utilities from the SDK package entrypoint. |
| packages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.ts | Updates graphcache resolvers to resolve Registry/Domain interface entities (incl. virtual registries). |
| packages/ensdb-sdk/src/lib/drizzle.test.ts | Updates schema cloning tests to expect unified domain table. |
| packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts | Refactors abstract schema: unified domain table, polymorphic registry, and updated relations/indexes. |
| apps/ensindexer/src/plugins/protocol-acceleration/handlers/ThreeDNSToken.ts | Updates ENSv1 domain ID creation to include registry context. |
| apps/ensindexer/src/plugins/protocol-acceleration/handlers/ENSv1Registry.ts | Canonicalizes ENSv1 registry and updates resolver-domain relationships for CAIP-shaped ENSv1 domain IDs. |
| apps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.ts | Refactors resolver event indexing to reuse ensureEvent and pass eventId. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/EnhancedAccessControl.ts | Refactors permissions event indexing to reuse ensureEvent and pass eventId. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts | Refactors domain/renewal event indexing to reuse ensureEvent and pass eventId. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts | Inserts registry/domain rows into unified tables and normalizes event indexing via ensureEvent. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts | Updates managed-name usage and ENSv1 domain ID creation; refactors event indexing via ensureEvent. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts | Updates ENSv1 domain ID creation and refactors event indexing via ensureEvent. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts | Implements ENSv1 virtual registries + unified domain upserts and refactors history events via ensureEvent. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts | Updates managed-name usage and ENSv1 domain ID creation; refactors event indexing via ensureEvent. |
| apps/ensindexer/src/lib/managed-names.ts | Replaces local managed-name implementation with thin wrappers around SDK helpers. |
| apps/ensindexer/src/lib/managed-names.test.ts | Updates managed-name tests to assert registry association and to use more flexible matchers. |
| apps/ensindexer/src/lib/ensv2/event-db-helpers.ts | Changes helpers to accept eventId (decoupling edge inserts from event creation). |
| apps/ensindexer/src/lib/ensv2/domain-db-helpers.ts | Updates ENSv1 effective-owner materialization to target unified domain table. |
| apps/ensapi/src/omnigraph-api/schema/registry.ts | Implements Registry as a loadable interface + concrete types; updates parents/domains querying to unified domain table. |
| apps/ensapi/src/omnigraph-api/schema/registry-permissions-user.ts | Updates registry field type to use the new Registry interface ref. |
| apps/ensapi/src/omnigraph-api/schema/registration.ts | Updates wrapped-registration tokenId resolution to work with new ENSv1 domain ID shape. |
| apps/ensapi/src/omnigraph-api/schema/query.ts | Replaces v1/v2 domain dev queries with allDomains; makes root non-null via getRootRegistryId; updates registry lookup semantics. |
| apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts | Updates tests for non-null root, registry polymorphism, ENSv1 node exposure, and canonical filtering. |
| apps/ensapi/src/omnigraph-api/schema/domain.ts | Refactors to unified Domain interface loading + canonical-path loader; adds parent and ENSv1 node. |
| apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts | Adds integration coverage for canonical path semantics (leaf→root) and alias collapsing. |
| apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts | Refactors forward traversal to operate from all configured root registries over the unified domain table. |
| apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts | Collapses v1/v2 canonical path logic into unified reverse traversal over the namegraph. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-registry.ts | Updates docs/semantics for unified registry filtering. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-parent.ts | Updates docs to reflect unified parent derivation. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts | Replaces v1/v2 union traversal with unified upward traversal via registry-canonical edges. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts | Switches canonical filtering to inner-join against canonical registry set for unified domains. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts | Removes v1/v2 unions; constructs a single base-domain set from unified domain table. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts | Builds canonical registry set by forward traversal from all configured roots over unified domains. |
| apps/ensapi/src/omnigraph-api/context.ts | Consolidates canonical-path DataLoader into one loader over unified DomainId. |
| apps/ensapi/src/lib/name-tokens/get-indexed-subregistries.ts | Simplifies indexed subregistries discovery using SDK managed-name helper + datasource contracts. |
| AGENTS.md | Updates contributor guidance (assertion patterns + schema generation reminders). |
| .changeset/unified-domain-model.md | Announces unified domain/registry model and breaking ID/schema changes. |
| .changeset/query-root-nonnull.md | Announces Query.root becoming non-null with v2-preferred fallback behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts (1)
245-250:⚠️ Potential issue | 🟠 MajorReuse the precomputed
eventIdininsertLatestRenewal.Line 249 re-calls
ensureEvent(context, event)even thougheventIdis already computed at Line 245. This adds an unnecessary DB write/read in a hot path.♻️ Proposed fix
await insertLatestRenewal(context, registration, { domainId, duration, - eventId: await ensureEvent(context, event), + eventId, // NOTE: no pricing information from BaseRegistrar#NameRenewed. in ENSv1, this info is // indexed from the Registrar Controllers, see apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts` around lines 245 - 250, The code recomputes the eventId by calling ensureEvent(context, event) twice; replace the second call with the already-computed eventId variable when calling insertLatestRenewal to avoid the duplicate DB work. In BaseRegistrar.ts, update the insertLatestRenewal call to pass the local eventId (from const eventId = await ensureEvent(context, event)) instead of awaiting ensureEvent(context, event) again so registration handling uses the precomputed eventId.
♻️ Duplicate comments (2)
packages/ensnode-sdk/src/shared/managed-names.ts (1)
204-219: 🧹 Nitpick | 🔵 TrivialAvoid duplicating the wrapper contract lookup here.
This still hard-codes the same
NameWrapperchecks thatgetContractsByManagedNamealready builds, so a future wrapper addition can drift out of sync. Reusing the managed-name group data or a shared cached wrapper set would keep the contract list in one place.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ensnode-sdk/src/shared/managed-names.ts` around lines 204 - 219, The isNameWrapper function duplicates lookup logic for "NameWrapper"; replace the manual checks with a single call to the existing managed-name resolver (e.g., getContractsByManagedName or the function that builds the managed-name group for wrappers) and test membership against that returned contract list (optionally caching the resulting Set for performance). Specifically, update isNameWrapper to call the shared function that returns the NameWrapper contracts for the given namespace (instead of calling getDatasourceContract/maybeGetDatasourceContract twice), then use accountIdEqual against the items in that list or a cached Set to determine and return true/false.apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts (1)
98-102:⚠️ Potential issue | 🟠 MajorParent
subregistryIdis set via UPDATE-only and can be dropped silently.If the parent domain row is missing when this event is processed, this UPDATE no-ops and the parent never gets linked to its subregistry, which breaks forward traversal until repaired.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts` around lines 98 - 102, The UPDATE against ensIndexerSchema.domain using context.ensDb.update({ id: parentDomainId }).set({ subregistryId: parentRegistryId }) can silently no-op if the parent domain row doesn't exist; change this to an upsert or existence-checked flow so the parent row is created when missing and then linked: either perform a select for parentDomainId and if absent insert a new domain row with id = parentDomainId and subregistryId = parentRegistryId, or use your DB client's upsert/insert-on-conflict/merge operation to set subregistryId atomically; ensure you modify the code around context.ensDb.update(...) (referencing ensIndexerSchema.domain, parentDomainId, parentRegistryId) to guarantee the parent domain is created when missing before or during setting subregistryId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts`:
- Around line 23-31: The JSDoc is out of date: update the comment describing the
recursive CTE to reflect that getRootRegistryIds(config.namespace) returns all
configured root registries, not just concrete ENSv1Registries and the ENSv2
Root; explicitly mention that it also includes the Basenames and Lineanames
virtual registries when configured so the description of which roots the
traversal starts from is accurate (update the JSDoc block above the canonical
registries CTE accordingly).
In `@apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts`:
- Around line 127-152: The exact leaf domain (deepest.domainId) can be lost if
an ancestor bridged resolver triggers recursion that returns null; to fix,
preserve the exact leaf id before recursing and use it as a fallback: when
computing deepestResolver and bridgesTo, store const fallbackId =
deepest.domainId (or similar), call resolveCanonicalDomainId(targetRegistryId,
targetPath, depth + 1) into a variable, and if that recursive call returns
null/undefined, return fallbackId instead of propagating null; update references
in this block around hasResolver, deepest, deepestResolver,
makeConcreteRegistryId and resolveCanonicalDomainId so the original exact match
is returned when recursion fails.
In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts`:
- Around line 383-385: The ExpiryExtended handler in NameWrapper calls
ensureEvent twice; replace the second redundant call with reuse of the
already-created eventId variable: after awaiting const eventId = await
ensureEvent(context, event) keep using that eventId when calling
ensureDomainEvent and any subsequent logic (avoid calling ensureEvent(context,
event) again), and apply the same fix to the second occurrence around the other
block (the sequence that currently re-computes eventId between lines like
407–411) so the code only creates the event once and reuses eventId for
ensureDomainEvent and downstream operations.
---
Outside diff comments:
In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts`:
- Around line 245-250: The code recomputes the eventId by calling
ensureEvent(context, event) twice; replace the second call with the
already-computed eventId variable when calling insertLatestRenewal to avoid the
duplicate DB work. In BaseRegistrar.ts, update the insertLatestRenewal call to
pass the local eventId (from const eventId = await ensureEvent(context, event))
instead of awaiting ensureEvent(context, event) again so registration handling
uses the precomputed eventId.
---
Duplicate comments:
In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts`:
- Around line 98-102: The UPDATE against ensIndexerSchema.domain using
context.ensDb.update({ id: parentDomainId }).set({ subregistryId:
parentRegistryId }) can silently no-op if the parent domain row doesn't exist;
change this to an upsert or existence-checked flow so the parent row is created
when missing and then linked: either perform a select for parentDomainId and if
absent insert a new domain row with id = parentDomainId and subregistryId =
parentRegistryId, or use your DB client's upsert/insert-on-conflict/merge
operation to set subregistryId atomically; ensure you modify the code around
context.ensDb.update(...) (referencing ensIndexerSchema.domain, parentDomainId,
parentRegistryId) to guarantee the parent domain is created when missing before
or during setting subregistryId.
In `@packages/ensnode-sdk/src/shared/managed-names.ts`:
- Around line 204-219: The isNameWrapper function duplicates lookup logic for
"NameWrapper"; replace the manual checks with a single call to the existing
managed-name resolver (e.g., getContractsByManagedName or the function that
builds the managed-name group for wrappers) and test membership against that
returned contract list (optionally caching the resulting Set for performance).
Specifically, update isNameWrapper to call the shared function that returns the
NameWrapper contracts for the given namespace (instead of calling
getDatasourceContract/maybeGetDatasourceContract twice), then use accountIdEqual
against the items in that list or a cached Set to determine and return
true/false.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 41345e0a-1b3a-4197-8be0-af4676362d8f
⛔ Files ignored due to path filters (1)
packages/enssdk/src/omnigraph/generated/schema.graphqlis excluded by!**/generated/**
📒 Files selected for processing (19)
.changeset/ensnode-sdk-managed-name-api.mdapps/ensapi/src/lib/protocol-acceleration/find-resolver.tsapps/ensapi/src/lib/resolution/forward-resolution.tsapps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.tsapps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.tsapps/ensapi/src/omnigraph-api/schema/resolver.tsapps/ensindexer/src/lib/ensv2/event-db-helpers.tsapps/ensindexer/src/lib/managed-names.test.tsapps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.tsapps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.tsapps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.tsapps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.tsapps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.tsapps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.tsapps/ensindexer/src/plugins/ensv2/handlers/ensv2/EnhancedAccessControl.tsapps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.tspackages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.tspackages/ensnode-sdk/src/shared/managed-names.tspackages/ensnode-sdk/src/shared/protocol-acceleration/is-bridged-resolver.ts
There was a problem hiding this comment.
Pull request overview
Refactors ENSNode’s domain/registry modeling to a unified, polymorphic “namegraph” across ENSv1 (including shadow registries) and ENSv2, aligning indexing, DB schema, and the Omnigraph GraphQL API around a single domain table and a polymorphic registry interface.
Changes:
- Unifies
v1Domain+v2Domaininto a singledomaintable (discriminated byDomainType) and makesRegistrypolymorphic (ENSv1 concrete, ENSv1 virtual, ENSv2). - Updates ID formats/builders (notably
ENSv1DomainIdnow includes the concrete ENSv1 registry; introduces ENSv1/ENSv2/virtual registry ID constructors). - Updates Omnigraph GraphQL schema + resolvers (new
Domain.parent, unified domain querying, registry polymorphism) and updates indexer handlers/tests accordingly.
Reviewed changes
Copilot reviewed 52 out of 54 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/enssdk/src/omnigraph/generated/schema.graphql | Updates Omnigraph SDL for Domain.parent, polymorphic Registry, allDomains, etc. |
| packages/enssdk/src/omnigraph/generated/introspection.ts | Regenerates introspection JSON to match updated schema types/interfaces. |
| packages/enssdk/src/lib/types/ensv2.ts | Splits RegistryId into ENSv1/ENSv2/virtual variants; changes ENSv1 ID branding/shape. |
| packages/enssdk/src/lib/ids.ts | Adds new registry/domain ID constructors; changes ENSv1 domain ID construction to include registry. |
| packages/ensnode-sdk/src/shared/types.ts | Adds RequiredAndNull helper type used for polymorphic narrowing. |
| packages/ensnode-sdk/src/shared/root-registry.ts | Adds getRootRegistryId/getRootRegistryIds/getENSv1RootRegistryId; refactors imports. |
| packages/ensnode-sdk/src/shared/protocol-acceleration/is-bridged-resolver.ts | Changes bridged-resolver detection return type to include { registry, shadow }. |
| packages/ensnode-sdk/src/shared/managed-names.ts | Introduces centralized managed-name mapping (name/node/concrete ENSv1 registry) + memoization. |
| packages/ensnode-sdk/src/registrars/lineanames-subregistry.ts | Removes per-subregistry helper (replaced by managed-name helpers). |
| packages/ensnode-sdk/src/registrars/index.ts | Stops exporting removed subregistry helper modules. |
| packages/ensnode-sdk/src/registrars/ethnames-subregistry.ts | Removes per-subregistry helper (replaced by managed-name helpers). |
| packages/ensnode-sdk/src/registrars/basenames-subregistry.ts | Removes per-subregistry helper (replaced by managed-name helpers). |
| packages/ensnode-sdk/src/index.ts | Re-exports new managed-name helpers. |
| packages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.ts | Updates registry entity resolution for polymorphic registry types and concrete-vs-virtual IDs. |
| packages/ensdb-sdk/src/lib/drizzle.test.ts | Updates schema/table assertions to reflect unified domain table. |
| packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts | Implements unified domains table, polymorphic registries, and updated relations/indexing model. |
| apps/ensindexer/src/plugins/protocol-acceleration/handlers/ThreeDNSToken.ts | Updates ENSv1 domain ID construction to include concrete registry. |
| apps/ensindexer/src/plugins/protocol-acceleration/handlers/ENSv1Registry.ts | Canonicalizes ENSv1 registry selection via managed names; updates ENSv1 domain IDs. |
| apps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.ts | Normalizes event insertion (ensureEvent) and updates ensureResolverEvent signature usage. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/EnhancedAccessControl.ts | Normalizes event insertion (ensureEvent) and updates ensurePermissionsEvent usage. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts | Normalizes event insertion and updates ensureDomainEvent usage to accept eventId. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts | Updates registry/domain inserts to new polymorphic tables + ID constructors; normalizes event usage. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts | Uses managed-name registry for ENSv1 domain IDs; normalizes domain-event insertion. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts | Uses managed-name registry for ENSv1 domain IDs; normalizes domain-event insertion. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts | Rewrites ENSv1 registry indexing into unified domain/registry tables incl. ENSv1 virtual registries. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts | Uses managed-name registry for ENSv1 domain IDs; normalizes event insertion and domain lookups. |
| apps/ensindexer/src/lib/managed-names.ts | Replaces indexer-local mapping with thin wrappers over SDK managed-name helpers. |
| apps/ensindexer/src/lib/managed-names.test.ts | Updates tests to validate new managed-name result shape and memoization behavior. |
| apps/ensindexer/src/lib/ensv2/event-db-helpers.ts | Changes ensure*Event helpers to accept a precomputed eventId. |
| apps/ensindexer/src/lib/ensv2/domain-db-helpers.ts | Updates ENSv1 owner materialization to target unified domain table. |
| apps/ensapi/src/omnigraph-api/schema/resolver.ts | Updates Resolver.bridged to return bridged target registry from new bridged-resolver API. |
| apps/ensapi/src/omnigraph-api/schema/registry.ts | Implements polymorphic Registry as a GraphQL interface with v1/v1-virtual/v2 concrete types. |
| apps/ensapi/src/omnigraph-api/schema/registry-permissions-user.ts | Updates registry reference type and ID constructor usage in permissions-user view. |
| apps/ensapi/src/omnigraph-api/schema/registration.ts | Fixes wrapped-registration tokenId derivation by loading domain and asserting ENSv1 domain type. |
| apps/ensapi/src/omnigraph-api/schema/query.ts | Replaces v1Domains/v2Domains test fields with unified allDomains; makes root non-null. |
| apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts | Updates tests for non-null root + registry polymorphism + ENSv1 domain node exposure. |
| apps/ensapi/src/omnigraph-api/schema/domain.ts | Reworks Domain as loadable interface over unified domain table; adds Domain.parent and ENSv1 node. |
| apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts | Adds canonical-path integration tests for leaf→root path and alias collapsing. |
| apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts | Rewrites name→domain lookup using canonical traversal + bridged-resolver recursion. |
| apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts | Replaces v1/v2 canonical-path helpers with unified reverse traversal using root registries list. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-registry.ts | Updates comments/behavior to reflect unified domain model. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-parent.ts | Updates comments to match unified parent derivation logic. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts | Unifies v1/v2 name filtering into a single recursive query over domain + registryCanonicalDomain. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts | Simplifies canonical filtering using canonical registries CTE + unified registry IDs. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts | Replaces unioned v1/v2 base sets with a single base set over unified domain table. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts | Rebuilds canonical registries set by forward-walking domain.subregistryId from all namespace roots. |
| apps/ensapi/src/omnigraph-api/context.ts | Replaces v1/v2 canonical-path loaders with a unified canonicalPath loader. |
| apps/ensapi/src/lib/resolution/forward-resolution.ts | Adapts bridged-resolver return type change (bridgesTo.registry). |
| apps/ensapi/src/lib/protocol-acceleration/find-resolver.ts | Removes lazy proxy; resolves ENSv1RegistryOld inline during indexed resolver lookup. |
| apps/ensapi/src/lib/name-tokens/get-indexed-subregistries.ts | Switches to managed-name helpers to compute subregistry managed nodes. |
| AGENTS.md | Updates contributor guidance for test assertion style + schema generation commands. |
| .changeset/unified-domain-model.md | Adds release note for unified polymorphic domain/registry model and breaking ID changes. |
| .changeset/query-root-nonnull.md | Adds release note for Query.root now being non-null with fallback behavior. |
| .changeset/ensnode-sdk-managed-name-api.md | Adds release note for new managed-name/root-registry helpers and removed registrar helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@greptile review |
|
@greptile review |
There was a problem hiding this comment.
Pull request overview
This PR refactors ENSNode’s data model and Omnigraph API to treat ENSv1/ENSv2 domains and registries as a unified polymorphic namegraph, addressing cross-registry conflicts and improving canonical traversal semantics.
Changes:
- Unifies
v1Domain+v2Domaininto a single polymorphicdomaintable and introduces polymorphicRegistry(ENSv1 concrete, ENSv1 virtual, ENSv2) end-to-end (DB → indexer → API → SDK). - Updates ID formats/helpers (notably
ENSv1DomainIdbecomes${ENSv1RegistryId}/${node}) and centralizes “managed name” + root-registry helpers in@ensnode/ensnode-sdk. - Updates GraphQL schema to expose
Domain.parent, newRegistryinterface implementations, and consolidates dev-only domain listing viaallDomains.
Reviewed changes
Copilot reviewed 52 out of 54 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/enssdk/src/omnigraph/generated/schema.graphql | Updates generated Omnigraph schema for polymorphic Registry, Domain.parent, and allDomains. |
| packages/enssdk/src/omnigraph/generated/introspection.ts | Updates generated introspection JSON to match the new schema shape/types. |
| packages/enssdk/src/lib/types/ensv2.ts | Refactors SDK type brands for polymorphic RegistryId and CAIP-shaped ENSv1DomainId. |
| packages/enssdk/src/lib/ids.ts | Adds new ID constructors (makeENSv1RegistryId, makeENSv1VirtualRegistryId, makeConcreteRegistryId, etc.). |
| packages/ensnode-sdk/src/shared/types.ts | Adds RequiredAndNull<> helper type used for discriminated polymorphic models. |
| packages/ensnode-sdk/src/shared/root-registry.ts | Adds getRootRegistryId(s) and ENSv1/v2 root registry ID helpers. |
| packages/ensnode-sdk/src/shared/protocol-acceleration/is-bridged-resolver.ts | Changes bridged-resolver detection to return { registry, shadow }. |
| packages/ensnode-sdk/src/shared/managed-names.ts | Introduces centralized managed-name resolution (name/node/registry) with memoization. |
| packages/ensnode-sdk/src/registrars/lineanames-subregistry.ts | Removes legacy Lineanames subregistry helper in favor of managed-name API. |
| packages/ensnode-sdk/src/registrars/ethnames-subregistry.ts | Removes legacy Ethnames subregistry helper in favor of managed-name API. |
| packages/ensnode-sdk/src/registrars/basenames-subregistry.ts | Removes legacy Basenames subregistry helper in favor of managed-name API. |
| packages/ensnode-sdk/src/registrars/index.ts | Stops exporting removed registrar-subregistry helpers. |
| packages/ensnode-sdk/src/index.ts | Exports the new managed-name helpers. |
| packages/enskit/src/react/omnigraph/_lib/by-id-lookup-resolvers.ts | Updates URQL graphcache by-id resolvers for polymorphic Registry types. |
| packages/ensdb-sdk/src/lib/drizzle.test.ts | Updates tests to reflect domain table rename/unification. |
| packages/ensdb-sdk/src/ensindexer-abstract/ensv2.schema.ts | Unifies DB schema: adds polymorphic domain + registry typing and relations. |
| apps/ensindexer/src/plugins/protocol-acceleration/handlers/ThreeDNSToken.ts | Updates ENSv1 domain ID construction to include registry context. |
| apps/ensindexer/src/plugins/protocol-acceleration/handlers/ENSv1Registry.ts | Canonicalizes ENSv1 registry for resolver relations; uses new ENSv1 domain IDs. |
| apps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.ts | Refactors event linking to reuse ensureEvent() and pass eventId through. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/EnhancedAccessControl.ts | Refactors permissions event linking to use ensureEvent() + new helper signatures. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ETHRegistrar.ts | Refactors event creation/linking to avoid redundant ensureEvent() calls. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv2/ENSv2Registry.ts | Updates registry/domain upserts for polymorphic tables and registry typing. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/RegistrarController.ts | Uses managed-name registry context for ENSv1 domain IDs and new event helper signatures. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts | Uses managed-name registry context for ENSv1 domain IDs; refactors event handling. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts | Builds ENSv1 virtual registries + unified domain rows; maintains canonical-domain links. |
| apps/ensindexer/src/plugins/ensv2/handlers/ensv1/BaseRegistrar.ts | Uses managed-name registry context for ENSv1 domain IDs; refactors event linking. |
| apps/ensindexer/src/lib/managed-names.ts | Converts to thin wrappers around SDK managed-name helpers bound to config.namespace. |
| apps/ensindexer/src/lib/managed-names.test.ts | Updates tests for new managed-name return shape (includes registry) and memoization semantics. |
| apps/ensindexer/src/lib/ensv2/event-db-helpers.ts | Changes helper signatures to accept eventId (callers ensure event once). |
| apps/ensindexer/src/lib/ensv2/domain-db-helpers.ts | Updates effective-owner materialization to write into unified domain table. |
| apps/ensapi/src/omnigraph-api/schema/resolver.ts | Updates Resolver.bridged semantics to use new bridged-resolver return shape. |
| apps/ensapi/src/omnigraph-api/schema/registry.ts | Implements polymorphic Registry GraphQL interface + concrete types. |
| apps/ensapi/src/omnigraph-api/schema/registry-permissions-user.ts | Updates registry resolution to target polymorphic Registry interface. |
| apps/ensapi/src/omnigraph-api/schema/registration.ts | Fixes Wrapped BaseRegistrar tokenId resolution under new ENSv1DomainId format. |
| apps/ensapi/src/omnigraph-api/schema/query.ts | Adds dev allDomains, updates registry and makes root non-null with v2→v1 preference. |
| apps/ensapi/src/omnigraph-api/schema/query.integration.test.ts | Updates integration tests for new root/registry polymorphism + ENSv1 node exposure. |
| apps/ensapi/src/omnigraph-api/schema/domain.ts | Unifies domain loading, adds parent, updates path to leaf→root canonical ordering. |
| apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts | Adds integration coverage for canonical Domain.path and alias collapsing. |
| apps/ensapi/src/omnigraph-api/lib/get-domain-by-interpreted-name.ts | Refactors forward namegraph lookup with DRR join + bridged-resolver recursion. |
| apps/ensapi/src/omnigraph-api/lib/get-canonical-path.ts | Unifies canonical-path traversal using registry-canonical-domain edges and multiple roots. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-registry.ts | Updates docs/behavior to reflect unified registry filtering across domain types. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-parent.ts | Updates docs/behavior to reflect unified parent derivation via registry canonical traversal. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-name.ts | Unifies name search traversal to operate on domain table only (no v1/v2 union). |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/filter-by-canonical.ts | Updates canonical filter to rely on canonical-registries CTE with unified registries. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/layers/base-domain-set.ts | Rebuilds the base domain set over unified domain table with derived parentId. |
| apps/ensapi/src/omnigraph-api/lib/find-domains/canonical-registries-cte.ts | Updates canonical-registry traversal to start from all namespace root registries. |
| apps/ensapi/src/omnigraph-api/context.ts | Replaces v1/v2 canonical-path loaders with a unified canonical-path loader. |
| apps/ensapi/src/lib/resolution/forward-resolution.ts | Updates accelerated bridged-resolver handling for new { registry, shadow } return. |
| apps/ensapi/src/lib/protocol-acceleration/find-resolver.ts | Removes lazy proxy for ENSv1RegistryOld and resolves it at query time. |
| apps/ensapi/src/lib/name-tokens/get-indexed-subregistries.ts | Replaces per-subregistry helpers with managed-name + datasource-based discovery. |
| AGENTS.md | Updates contributor guidance around test assertion patterns and generation steps. |
| .changeset/unified-domain-model.md | Documents breaking schema/ID changes and new GraphQL polymorphism. |
| .changeset/query-root-nonnull.md | Documents Query.root becoming non-null with v2→v1 preference. |
| .changeset/ensnode-sdk-managed-name-api.md | Documents removal of old subregistry helpers and new managed-name/root helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| sql`( | ||
| WITH RECURSIVE upward_check AS ( | ||
| -- Base case: find the deepest children (leaves of the concrete path) | ||
| -- Base case: find the deepest children (leaves of the concrete path) and walk one step | ||
| -- up via registryCanonicalDomain. The parent.subregistry_id = d.registry_id clause | ||
| -- performs edge authentication. | ||
| SELECT | ||
| d.id AS leaf_id, | ||
| d.parent_id AS current_id, | ||
| parent.id AS current_id, | ||
| 1 AS depth | ||
| FROM ${ensIndexerSchema.v1Domain} d | ||
| WHERE d.label_hash = (${rawLabelHashPathArray})[${pathLength}] | ||
|
|
||
| UNION ALL | ||
|
|
||
| -- Recursive step: traverse UP, verifying each ancestor's labelHash | ||
| SELECT | ||
| upward_check.leaf_id, | ||
| pd.parent_id AS current_id, | ||
| upward_check.depth + 1 | ||
| FROM upward_check | ||
| JOIN ${ensIndexerSchema.v1Domain} pd | ||
| ON pd.id = upward_check.current_id | ||
| WHERE upward_check.depth < ${pathLength} | ||
| AND pd.label_hash = (${rawLabelHashPathArray})[${pathLength} - upward_check.depth] | ||
| ) | ||
| SELECT leaf_id, current_id AS head_id | ||
| FROM upward_check | ||
| WHERE depth = ${pathLength} | ||
| ) AS v1_path_check`, | ||
| ) | ||
| .as("v1_path"); | ||
| } | ||
|
|
||
| /** | ||
| * Compose a query for v2Domains that have the specified children path. | ||
| * | ||
| * For a search like "sub1.sub2.paren": | ||
| * - concrete = ["sub1", "sub2"] | ||
| * - partial = 'paren' | ||
| * - labelHashPath = [labelhash('sub2'), labelhash('sub1')] | ||
| * | ||
| * We find v2Domains matching the concrete path and return both: | ||
| * - leafId: the deepest child (label "sub1") - the autocomplete result, for ownership check | ||
| * - headId: the parent of the path (whose label should match partial "paren") | ||
| * | ||
| * Algorithm: Start from the deepest child (leaf) and traverse UP via registryCanonicalDomain. | ||
| * For v2, parent relationship is: domain.registryId -> registryCanonicalDomain -> parent domainId | ||
| */ | ||
| function v2DomainsByLabelHashPath(labelHashPath: LabelHashPath) { | ||
| // If no concrete path, return all domains (leaf = head = self) | ||
| // Postgres will optimize this simple subquery when joined | ||
| if (labelHashPath.length === 0) { | ||
| return ensDb | ||
| .select({ | ||
| leafId: sql<ENSv2DomainId>`${ensIndexerSchema.v2Domain.id}`.as("leafId"), | ||
| headId: sql<ENSv2DomainId>`${ensIndexerSchema.v2Domain.id}`.as("headId"), | ||
| }) | ||
| .from(ensIndexerSchema.v2Domain) | ||
| .as("v2_path"); | ||
| } | ||
|
|
||
| // NOTE: using new Param as per https://github.com/drizzle-team/drizzle-orm/issues/1289#issuecomment-2688581070 | ||
| const rawLabelHashPathArray = sql`${new Param(labelHashPath)}::text[]`; | ||
| const pathLength = sql`array_length(${rawLabelHashPathArray}, 1)`; | ||
|
|
||
| // Use a recursive CTE starting from the deepest child and traversing UP | ||
| // The query: | ||
| // 1. Starts with domains matching the leaf labelHash (deepest child) | ||
| // 2. Recursively joins parents via registryCanonicalDomain, verifying each ancestor's labelHash | ||
| // 3. Returns both the leaf (for result/ownership) and head (for partial match) | ||
| return ensDb | ||
| .select({ | ||
| // https://github.com/drizzle-team/drizzle-orm/issues/1242 | ||
| leafId: sql<ENSv2DomainId>`v2_path_check.leaf_id`.as("leafId"), | ||
| headId: sql<ENSv2DomainId>`v2_path_check.head_id`.as("headId"), | ||
| }) | ||
| .from( | ||
| sql`( | ||
| WITH RECURSIVE upward_check AS ( | ||
| -- Base case: find the deepest children (leaves of the concrete path) | ||
| -- and get their parent via registryCanonicalDomain | ||
| -- Note: JOIN (not LEFT JOIN) is intentional - we only match domains | ||
| -- with a complete canonical path to the searched FQDN | ||
| SELECT | ||
| d.id AS leaf_id, | ||
| rcd.domain_id AS current_id, | ||
| 1 AS depth | ||
| FROM ${ensIndexerSchema.v2Domain} d | ||
| FROM ${ensIndexerSchema.domain} d | ||
| JOIN ${ensIndexerSchema.registryCanonicalDomain} rcd | ||
| ON rcd.registry_id = d.registry_id | ||
| JOIN ${ensIndexerSchema.v2Domain} rcd_parent | ||
| ON rcd_parent.id = rcd.domain_id AND rcd_parent.subregistry_id = d.registry_id | ||
| JOIN ${ensIndexerSchema.domain} parent | ||
| ON parent.id = rcd.domain_id AND parent.subregistry_id = d.registry_id | ||
| WHERE d.label_hash = (${rawLabelHashPathArray})[${pathLength}] | ||
|
|
||
| UNION ALL | ||
|
|
||
| -- Recursive step: traverse UP via registryCanonicalDomain | ||
| -- Note: JOIN (not LEFT JOIN) is intentional - see base case comment | ||
| -- Recursive step: traverse UP via registryCanonicalDomain, verifying each ancestor's | ||
| -- labelHash. The np.subregistry_id = pd.registry_id clause performs edge authentication. | ||
| SELECT | ||
| upward_check.leaf_id, | ||
| rcd.domain_id AS current_id, | ||
| np.id AS current_id, | ||
| upward_check.depth + 1 | ||
| FROM upward_check | ||
| JOIN ${ensIndexerSchema.v2Domain} pd | ||
| JOIN ${ensIndexerSchema.domain} pd | ||
| ON pd.id = upward_check.current_id | ||
| JOIN ${ensIndexerSchema.registryCanonicalDomain} rcd | ||
| ON rcd.registry_id = pd.registry_id | ||
| JOIN ${ensIndexerSchema.v2Domain} rcd_parent | ||
| ON rcd_parent.id = rcd.domain_id AND rcd_parent.subregistry_id = pd.registry_id | ||
| JOIN ${ensIndexerSchema.domain} np | ||
| ON np.id = rcd.domain_id AND np.subregistry_id = pd.registry_id | ||
| WHERE upward_check.depth < ${pathLength} | ||
| AND pd.label_hash = (${rawLabelHashPathArray})[${pathLength} - upward_check.depth] | ||
| ) | ||
| SELECT leaf_id, current_id AS head_id | ||
| FROM upward_check | ||
| WHERE depth = ${pathLength} | ||
| ) AS v2_path_check`, | ||
| ) AS domain_path_check`, | ||
| ) | ||
| .as("v2_path"); | ||
| .as("domain_path"); |
There was a problem hiding this comment.
FQDN searches silently return empty results for 2+ label names
The new domainsByLabelHashPath traverses upward via registryCanonicalDomain, but concrete ENSv1Registry rows (TLDs) have no registryCanonicalDomain entry — only virtual registries do. When pathLength ≥ 2 and the recursive step reaches a TLD (e.g. eth), the JOIN on rcd.registry_id = pd.registry_id finds no row, so depth = pathLength is never reached, and the CTE returns nothing.
Concretely, searching sub.eth. (FQDN — concrete=['sub','eth'], pathLength=2) traverses:
- depth=1:
sub.eth→ethvia virtual-registry RCD ✓ - depth=2:
eth(registryId = concrete ENSv1Registry) → no RCD entry → JOIN fails
WHERE depth = 2 matches nothing; the query returns 0 rows.
The old v1DomainsByLabelHashPath used d.parent_id which handled null parents naturally: the recursive step for eth produced head_id = null, depth=2 was reached, and the result was returned correctly (with an empty partial filter). The new unified code removes parent_id but doesn't add a fallback for when the upward walk terminates at a root registry.
closes #205
closes #1511
closes #1877